package com.nulldev.util.concurrency.threadIt.v4.debugger;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

import com.nulldev.util.JVM.Exceptions;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.concurrency.threadIt.v4.asyncTasks.AsyncTaskCallback;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.cntft.ControllableFutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.ExecutorFeatureExtensions;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.ExtendedExecutor;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.IExecutorUnwinder;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ft.FutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.graphics.renderIt.api.renderItConstants;
import com.nulldev.util.graphics.renderIt.api.renderItGraphicsInterface;
import com.nulldev.util.graphics.renderIt.api.core.IRenderCallback;
import com.nulldev.util.graphics.renderIt.api.core.IRenderCallbackMetadata;
import com.nulldev.util.graphics.renderIt.api.core.RITFont;
import com.nulldev.util.graphics.renderIt.ginterfaces.nitrous.NitrousRenderPane;
import com.nulldev.util.graphics.renderIt.renderer.global.rColor;
import com.nulldev.util.graphics.renderIt.renderer.global.rColors;
import com.nulldev.util.gui.fontAPI.FontManager;
import com.nulldev.util.gui.swingAPI.Swing;

public class threadItUnwinder extends JFrame implements IExecutorUnwinder, IRenderCallback {

	private static final long serialVersionUID = -4757087438303371101L;
	private JPanel contentPane;
	private final NitrousRenderPane nrp;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		Swing.enableAA();
		Swing.enableJSO();
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					final threadItUnwinder frame = new threadItUnwinder();
					frame.setVisible(true);
					Swing.center(frame);
					frame.nrp.start();
					if (ExecutorManager.get().executor() instanceof ExtendedExecutor) {
						if (((ExtendedExecutor) ExecutorManager.get().executor()).supportsFeature(ExecutorFeatureExtensions.EEX_extended_unwinder_debugging)) {
							((ExtendedExecutor) ExecutorManager.get().executor()).hookUnwinder(frame);
							JVM.println("Enabled unwinder.");
						} else
							JVM.errorln("Executor does NOT support the unwinded debugging extension.");
					} else
						JVM.errorln("Executor does NOT support the extended execution API.");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the frame.
	 */
	public threadItUnwinder() {
		setTitle("threadIt! 4.0 Unwinder");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 720, 400);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(0, 0, 0, 0));
		setContentPane(contentPane);
		contentPane.setLayout(new BorderLayout(0, 0));

		this.nrp = new NitrousRenderPane(this);
		contentPane.add(nrp, BorderLayout.CENTER);

		nrp.setRenderCallback(this);
		nrp.setDeferred(true);
		nrp.start(this.es);
	}

	private final LongAdder submittedTasks = new LongAdder(), eventLambdas = new LongAdder(), miscCalls = new LongAdder(), batchedTasks = new LongAdder(),
			queuedTasks = new LongAdder();
	private final LongAdder FTTasks = new LongAdder(), FTSuccessful = new LongAdder(), FTFailed = new LongAdder();
	private final LongAdder CNTFTTasks = new LongAdder(), CNTFTSuccessful = new LongAdder(), CNTFTFailed = new LongAdder();
	private final AtomicBoolean firstRun = new AtomicBoolean();
	private final int TASKS_SIZE = 24;
	private final LinkedBlockingQueue<Executable<?>> tasks = new LinkedBlockingQueue<Executable<?>>(TASKS_SIZE) {
		private static final long serialVersionUID = 5669439864782212189L;

		public boolean add(com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable<?> e) {
			if (size() >= TASKS_SIZE)
				remove();
			return super.add(e);
		};

		public boolean offer(com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable<?> e) {
			if (size() >= TASKS_SIZE)
				remove();
			return super.offer(e);
		};

		public void put(com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable<?> e) throws InterruptedException {
			if (size() >= TASKS_SIZE)
				remove();
			super.put(e);
		};
	};

	@Override
	public void newTasks(final Queue<Runnable> x, int amount) {
		if (x == null || x.isEmpty())
			return;
		if (amount <= 0)
			return;
		this.batchedTasks.add(amount);
		Runnable r, prev = null;
		while ((r = x.peek()) != null && r != prev) {
			this.newTask(r, true);
			prev = r;
		}
	}

	@Override
	public void newTasks(Runnable... _r) {
		if (_r == null || _r.length == 0)
			return;
		this.batchedTasks.add(_r.length);
		for (final Runnable r : _r)
			this.newTask(r);
	}

	@Override
	public void newTasks(List<Runnable> _r) {
		if (_r == null || _r.isEmpty())
			return;
		this.batchedTasks.add(_r.size());
		for (final Runnable r : _r)
			this.newTask(r);
	}

	private final ExecutorService es = Executors.newFixedThreadPool(9);

	@SuppressWarnings(
		{ "rawtypes", "unchecked" })
	@Override
	public void newTask(final Runnable r, final boolean isBatched) {
		if (r == null)
			return;
		es.execute(() -> {
			this.submittedTasks.increment();
			if (r instanceof ControllableFutureTask<?>) {
				this.CNTFTTasks.increment();
				final ControllableFutureTask<?> o = (ControllableFutureTask<?>) r;
				o.addCallbackTask(new AsyncTaskCallback() {

					@Override
					public void onCompletion(Object result) {
						queuedTasks.decrement();
						CNTFTSuccessful.increment();
						tasks.remove(o);
					}

					@Override
					public void onError(Throwable e) {
						queuedTasks.decrement();
						CNTFTFailed.increment();
						tasks.remove(o);
					}

					@Override
					public void onQueued() {
						queuedTasks.increment();
					}

					@Override
					public void onSubmissionRejection() {
						queuedTasks.decrement();
						FTFailed.increment();
						tasks.remove(o);
					}
				});
				try {
					tasks.put(o);
				} catch (InterruptedException e1) {
				}
			} else if (r instanceof FutureTask<?>) {
				this.FTTasks.increment();
				final FutureTask<?> o = (FutureTask<?>) r;
				o.addCallbackTask(new AsyncTaskCallback() {

					@Override
					public void onCompletion(Object result) {
						queuedTasks.decrement();
						FTSuccessful.increment();
						tasks.remove(o);
					}

					@Override
					public void onError(Throwable e) {
						queuedTasks.decrement();
						FTFailed.increment();
						tasks.remove(o);
					}

					@Override
					public void onQueued() {
						queuedTasks.increment();
					}

					@Override
					public void onSubmissionRejection() {
						queuedTasks.decrement();
						FTFailed.increment();
						tasks.remove(o);
					}
				});
				try {
					tasks.put(o);
				} catch (InterruptedException e1) {
				}
			} else if (r.toString().contains("$$Lambda$")) {
				this.eventLambdas.increment();
			} else {
				this.miscCalls.increment();
			}
			this.nrp.signalRepaint();
		});
	}

	private RITFont systemFont, largeFont;

	@Override
	public void onRender(final renderItGraphicsInterface iface, final IRenderCallbackMetadata metadata) {
		if (iface == null)
			return;
		final int width = iface.viewport_getWidth(), height = iface.viewport_getHeight();
		iface.canvas_clear(0, 0, width, height, rColors.gray);
		if (!this.firstRun.get()) {
			JVM.println(iface.rit_get(renderItConstants.RIT_RENDERER_NAME));
			ArrayUtils.println(iface.supportedOperations());

			if (!iface.text_isAAPresent())
				iface.text_setAAPresence(true);
			this.systemFont = iface.awt_make_font(FontManager.getFontSetAsAWT(18));
			this.largeFont = iface.awt_make_font(FontManager.getFontSetAsAWT(24));
			iface.swing_setRenderMethod(renderItConstants.RIT_SWING_PANEL_REPAINT_WITHIN_SYNC);
			this.firstRun.set(true);
		}
		iface.bd_fillRect(width - 15, height - 15, 15, 15, rColor.random());
		iface.bd_fillRect(0, 0, width, 17, rColors.black);
		iface.text_shadowedString("threadIt! 4.0: Thread Unwinder", 1, 15, systemFont);
		iface.text_shadowedString("Submitted Tasks: " + this.submittedTasks + " (" + this.batchedTasks + " batched)", 15, 45, largeFont);
		iface.text_shadowedString("FutureTask: ", 15, 85, largeFont);
		iface.text_shadowedString("Tasks: " + this.FTTasks, 30, 110, systemFont);
		iface.text_shadowedString("Completed: " + this.FTSuccessful, 30, 130, systemFont);
		iface.text_shadowedString("Failed: " + this.FTFailed, 30, 150, systemFont);
		iface.text_shadowedString("CNTFT: ", 200, 85, largeFont);
		iface.text_shadowedString("Tasks: " + this.CNTFTTasks, 215, 110, systemFont);
		iface.text_shadowedString("Completed: " + this.CNTFTSuccessful, 215, 130, systemFont);
		iface.text_shadowedString("Failed: " + this.CNTFTFailed, 215, 150, systemFont);
		iface.text_shadowedString("Lambdas: ", 400, 85, largeFont);
		iface.text_shadowedString("Count: " + this.eventLambdas, 415, 110, systemFont);
		iface.text_shadowedString("Misc: ", 400, 135, largeFont);
		iface.text_shadowedString("Count: " + this.miscCalls, 415, 155, systemFont);
		iface.text_shadowedString("Active Tasks: (" + this.tasks.size() + " active, " + this.queuedTasks + " queued)", 15, 180, largeFont);
		int y = 200;
		for (final Object _o : this.tasks.toArray()) {
			final Executable<?> o = (Executable<?>) _o;
			iface.text_shadowedString(o.toString(), 30, y, systemFont);
			final List<AsyncTaskCallback<?>> cbs = o.callbacks();
			if (!cbs.isEmpty()) {
				y += 17;
				iface.text_shadowedString("Callbacks: ", 45, y, systemFont);
				for (final AsyncTaskCallback<?> cb : cbs) {
					y += 17;
					iface.text_shadowedString(cb.toString(), 60, y, systemFont);
				}
			}
			if (o instanceof FutureTask<?>) {
				final String n = Exceptions.toString(((FutureTask<?>) o).getTrace());
				// TODO
			}
			y += 17;
		}
	}
}
